Skip to content

Comments

fix: load genesis information from config.yaml#101

Merged
pablodeymo merged 6 commits intomainfrom
fix-genesis-parsing
Feb 9, 2026
Merged

fix: load genesis information from config.yaml#101
pablodeymo merged 6 commits intomainfrom
fix-genesis-parsing

Conversation

@MegaRedHand
Copy link
Collaborator

@MegaRedHand MegaRedHand commented Feb 9, 2026

We're currently loading the genesis timestamp from the genesis.json file and the validators from the annotated_validators.yaml file, both generated by lean-quickstart at devnet startup, along with the other network configuration files. However, the other clients load the genesis configuration from the config.yaml file alone, which contains the list of validator pubkeys, ordered by index.

We recently had a devnet that failed to finalize due to ethlambda deriving a different genesis state from the other nodes. We suspect this happened because the annotated_validators.yaml file had an extra validator. To avoid this happening again, this PR makes ethlambda load the genesis configuration from the config.yaml file, like the other clients.

It also adds a setup log with the genesis time and number of validators.

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

🤖 Kimi Code Review

Review Summary

This PR migrates the genesis configuration from JSON to YAML format, which is a good move for consistency with other configuration files. The changes are generally well-structured, but I've identified several issues that need attention.

Critical Issues

1. State Root Calculation Bug (High Priority)

File: crates/common/types/src/genesis.rs:137-143

The test state_from_genesis_root has hardcoded expected state and block roots that will break if any genesis parameters change. This is fragile and could mask consensus failures. The test should either:

  • Calculate expected roots dynamically based on the genesis data
  • Or be marked as #[ignore] with a clear comment about its purpose

2. Missing Validation (Medium Priority)

File: crates/common/types/src/genesis.rs:45-46

The deser_hex_pubkeys function should validate that there are no duplicate pubkeys in the genesis validator set, as this could lead to:

  • Multiple validators with the same identity
  • Potential slashing issues
  • Consensus divergence

Add validation:

let mut seen = std::collections::HashSet::new();
for pubkey in &pubkeys {
    if !seen.insert(pubkey) {
        return Err(D::Error::custom("Duplicate validator pubkey in genesis"));
    }
}

3. Error Handling Inconsistency (Medium Priority)

File: bin/ethlambda/src/main.rs:85-90

The error handling for genesis configuration is inconsistent:

  • Uses .expect() for file reading and parsing
  • But other configuration files (like bootnodes) might have more graceful handling

Consider using proper error propagation with ? operator and user-friendly error messages.

Minor Issues

4. Dead Code Warning (Low Priority)

File: bin/ethlambda/src/main.rs:168-175

The AnnotatedValidator struct has an #[allow(dead_code)] attribute for the pubkey field, but it's actually used in the deserialization process. The comment is misleading - the field is needed for deserialization even if not directly accessed.

5. Test Data Duplication (Low Priority)

File: crates/common/types/src/genesis.rs

The test constants PUBKEY_A, PUBKEY_B, PUBKEY_C are defined but also duplicated in the YAML string. Consider using format! macro to avoid duplication.

6. Missing Documentation (Low Priority)

File: crates/common/types/src/genesis.rs:8-14

The GenesisConfig struct fields could use documentation comments explaining:

  • Expected format of the hex strings (with/without 0x prefix)
  • That validator indices are assigned sequentially
  • Any constraints on genesis_time

Positive Changes

Good: Migration from JSON to YAML for consistency with other config files
Good: Simplified genesis configuration by removing redundant fields
Good: Added comprehensive tests for genesis configuration parsing
Good: Clean separation between configuration and state initialization

Recommendations

  1. Fix the state root test to be more robust
  2. Add duplicate pubkey validation in genesis config
  3. Improve error messages for genesis configuration loading
  4. Consider adding a genesis configuration schema validation step

The PR is generally well-structured but needs attention to the state root test and validation concerns before merging.


Automated review by Kimi (Moonshot AI) · custom prompt

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

🤖 Codex Code Review

Findings

  • High — potential validator index/key mismatch. Genesis validators now come from config.yaml and are assigned sequential indices, but validator secret keys are still loaded by index from annotated_validators.yaml. If the list order in config.yaml doesn’t exactly match the indices used in annotated_validators.yaml, the node can sign with the wrong key or fail to sign at all. Add a cross-check that each annotated_validators.yaml entry’s pubkey matches genesis_config.genesis_validators[index] (or derive validators from the annotated file instead). bin/ethlambda/src/main.rs:98-103, bin/ethlambda/src/main.rs:193-235, crates/common/types/src/genesis.rs:14-24
  • Medium — no duplicate/consistency validation for genesis validators. The prior read_validators path deduped by index and errored on duplicates. Now there is no check for duplicate pubkeys, empty validator list, or mismatch vs validator_keys count. This can lead to an invalid genesis state root or subtle operational failures. Consider validating uniqueness and ensuring at least one validator, and optionally validators.len() == validator_keys.len() for the local node. crates/common/types/src/genesis.rs:14-24, bin/ethlambda/src/main.rs:98-103
  • Low — config typos are silently ignored. GenesisConfig ignores unknown YAML fields by default, which can mask operator mistakes (e.g., a misspelled GENESIS_VALIDATORS). If you want stricter config handling, add #[serde(deny_unknown_fields)] or a validation pass. crates/common/types/src/genesis.rs:5-12

Questions / assumptions

  • Is it guaranteed by tooling that config.yaml order exactly matches annotated_validators.yaml indices? If not, I’d treat the mismatch risk above as a correctness/security issue.

Looks good overall beyond the configuration/index consistency concerns.

Next steps if you want:

  1. I can implement the pubkey/index cross-check and uniqueness validation.
  2. I can add strict config validation (deny unknown fields) and better error messages.

Automated review by OpenAI Codex · custom prompt

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

🤖 Claude Code Review

Based on my review of PR #101, here is my code review:


PR #101 Code Review: Load genesis from config.yaml

Summary

This PR migrates genesis configuration loading from genesis.json to config.yaml to match cross-client compatibility (Ream/Zeam). The change addresses a real devnet issue where mismatched genesis state prevented finalization.

Critical Issues

🔴 Hex Decoding Error Handling (genesis.rs:37-43)

fn deser_hex_pubkeys<'de, D>(d: D) -> Result<Vec<ValidatorPubkeyBytes>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    use serde::de::Error;

    let hex_strings: Vec<String> = Vec::deserialize(d)?;
    hex_strings
        .into_iter()
        .map(|s| {
            let s = s.strip_prefix("0x").unwrap_or(&s);  // ← Safe
            let bytes = hex::decode(s)
                .map_err(|_| D::Error::custom("GENESIS_VALIDATORS value is not valid hex"))?;
            bytes
                .try_into()
                .map_err(|_| D::Error::custom("GENESIS_VALIDATORS pubkey length != 52"))
        })
        .collect()
}

Issue: Error messages lose context about which validator pubkey failed.

Recommendation: Include the index and hex string in error messages:

.enumerate()
.map(|(idx, s)| {
    let s = s.strip_prefix("0x").unwrap_or(&s);
    let bytes = hex::decode(s)
        .map_err(|_| D::Error::custom(format!("GENESIS_VALIDATORS[{}] is not valid hex: {}", idx, s)))?;
    bytes
        .try_into()
        .map_err(|v: Vec<u8>| D::Error::custom(format!("GENESIS_VALIDATORS[{}] has length {} (expected 52)", idx, v.len())))
})

Medium Issues

🟡 Index Casting Without Validation (genesis.rs:21)

pub fn validators(&self) -> Vec<Validator> {
    self.genesis_validators
        .iter()
        .enumerate()
        .map(|(i, pubkey)| Validator {
            pubkey: *pubkey,
            index: i as u64,  // ← Unchecked cast
        })
        .collect()
}

Issue: On 32-bit systems or with corrupt configs, usize to u64 cast could theoretically overflow (unlikely but not impossible).

Recommendation: Add bounds check or use u64::try_from(i):

index: u64::try_from(i).expect("validator index overflow"),

🟡 Test Root Hash Pin Without Justification (genesis.rs:134-149)

// Pin the state root so changes are caught immediately.
let expected =
    hex::decode("362db4ffe968f1d100934797f6d3c7985b4aee9d96b328ad2e47243b8292e434")
        .unwrap();
assert_eq!(root.as_slice(), &expected[..], "state root mismatch");

Issue: This test will break silently if SSZ serialization or tree hashing changes upstream. Consider documenting:

  • How this hash was derived
  • Whether it matches other client implementations
  • Whether it needs updating if state structure changes

Recommendation: Add a comment explaining the hash origin and cross-client verification status.

Minor Issues

🟢 Unused Field Warning (main.rs:171)

#[allow(dead_code)] // Present in YAML, needed for deserialization but not read in code
#[serde(rename = "pubkey_hex")]
#[serde(deserialize_with = "deser_pubkey_hex")]
pubkey: ValidatorPubkeyBytes,

Observation: The pubkey field in AnnotatedValidator is deserialized but never used since validators now come from GenesisConfig. Consider removing it entirely or documenting why it must remain for compatibility.

🟢 Test Variable Naming (genesis.rs:59)

const TEST_CONFIG_JSON: &str = r#"# Genesis Settings

Minor: Variable is named TEST_CONFIG_JSON but contains YAML. Rename to TEST_CONFIG_YAML.

🟢 Duplicate Validator Deduplication Removed

The old read_validators() function included duplicate index detection:

validators.dedup_by_key(|v| v.index);
if validators.len() != num_validators {
    panic!("Duplicate validator indices found in config");
}

This check is now implicitly safe since enumerate() generates sequential indices. Document this assumption in the code or CLAUDE.md since it's security-relevant.

Positive Observations

Cross-Client Compatibility: Adopting the Ream/Zeam config format is the right move for multi-client devnets.

Comprehensive Tests: The new tests cover deserialization, state defaults, and state root pinning.

Simplified Code: Removing the separate annotated_validators.yaml parsing reduces complexity and eliminates the root cause of the finalization bug.

Proper Default Implementation: Adding Default to Checkpoint is idiomatic and correctly initializes zero values.

Logging Addition: The setup log at main.rs:89-93 improves observability.

Security Considerations

  1. Genesis State Consistency: The PR correctly ensures all clients derive identical genesis states by using the same config format. This is critical for consensus correctness.

  2. Validator Index Assignment: Sequential index assignment (0, 1, 2, ...) from array order is secure and deterministic.

  3. No Signature Verification: Genesis blocks have no signatures (mentioned in CLAUDE.md) — confirm this is still true after the change.

Testing Recommendations

  1. Run the new tests in genesis.rs
  2. Verify multi-client devnet interop with Ream/Zeam using the new config format
  3. Test with malformed config.yaml to ensure error messages are helpful
  4. Confirm the state root hash matches other implementations

Verdict

APPROVE with recommendations. The core change is sound and addresses a real production issue. The error handling improvements are important for debugging future config issues, but not blockers.


Automated review by Claude (Anthropic) · custom prompt

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 9, 2026

Greptile Overview

Greptile Summary

This PR fixes a critical consensus issue by standardizing genesis state derivation across all clients. The changes eliminate the dependency on annotated_validators.yaml (which caused a devnet failure due to an extra validator) and instead load genesis configuration from config.yaml matching other clients' behavior.

Key changes:

  • Replaced genesis.json + annotated_validators.yaml with single source of truth: config.yaml
  • Validator indices now assigned sequentially (0, 1, 2...) based on array order in GENESIS_VALIDATORS
  • Removed old Genesis struct and read_validators() function that parsed explicit indices
  • Simplified State::from_genesis() to accept genesis_time directly and initialize all checkpoints to default values
  • Added genesis info logging at startup showing time and validator count
  • Comprehensive test coverage including state root validation to catch future drift

Impact:
This change ensures deterministic genesis state derivation and prevents future consensus failures caused by misconfigured validator files.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The refactoring is well-structured with comprehensive test coverage that validates genesis state root hashes, ensuring consensus compatibility. The change simplifies the codebase by removing ~30 lines of complex validation logic and replacing it with deterministic sequential index assignment. All tests verify the state root matches expected values, catching any future drift.
  • No files require special attention

Important Files Changed

Filename Overview
crates/common/types/src/genesis.rs Complete refactor from Genesis struct to GenesisConfig with YAML deserialization, adds comprehensive tests including state root validation
bin/ethlambda/src/main.rs Switches from genesis.json to config.yaml, removes read_validators function, adds genesis info logging
crates/common/types/src/state.rs Simplified State::from_genesis to accept genesis_time directly, initializes checkpoints to default values, added Default derive to Checkpoint

Sequence Diagram

sequenceDiagram
    participant Main as main.rs
    participant FS as File System
    participant GC as GenesisConfig
    participant State as State
    participant Store as Store

    Main->>FS: Read config.yaml
    FS-->>Main: YAML content
    Main->>GC: Deserialize YAML to GenesisConfig
    GC->>GC: Parse GENESIS_TIME
    GC->>GC: Parse GENESIS_VALIDATORS (hex pubkeys)
    GC-->>Main: GenesisConfig instance
    Main->>Main: Log genesis_time & validator_count
    Main->>GC: Call validators()
    GC->>GC: Enumerate validators, assign sequential indices (0, 1, 2...)
    GC-->>Main: Vec<Validator>
    Main->>State: from_genesis(genesis_time, validators)
    State->>State: Initialize with default checkpoints
    State-->>Main: Genesis State
    Main->>Store: from_genesis(backend, genesis_state)
    Store-->>Main: Store instance
Loading

@pablodeymo pablodeymo merged commit 4339b92 into main Feb 9, 2026
3 checks passed
@pablodeymo pablodeymo deleted the fix-genesis-parsing branch February 9, 2026 16:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants